Architecture and When to Use Which Firebase Service
Authentication
User identity and simple profile management. Use provider-based sign-in (Google/Apple) for fast onboarding; phone auth for phone-first markets.
Firestore
Document DB with real-time sync and offline support; excellent for structured, hierarchical data and queries. Prefer for most app data unless you need extremely low-latency counters or legacy Realtime Database use cases.
Realtime Database
Lower-level JSON tree; use when you need extremely low-latency fan-out or are already invested in its rules/structure.
Cloud Storage
Large binary blobs (images, videos). Use signed URLs and security rules to control access.
Cloud Functions
Secure server-side logic (webhooks, triggered tasks, image resizing, custom claims) and enforce server-side validation.
Cloud Messaging (FCM)
Push notifications for background/foreground messaging and data-only messages for app sync.
Analytics & Crashlytics
Event-driven instrumentation and crash monitoring; tie releases to crash reports.
Remote Config
Feature flags and A/B tests via Firebase console.
Design rule: Keep business-critical validation in Cloud Functions and security rules; do not trust client-only checks.
Authentication Patterns and Secure Token Handling
- Email/Password: standard sign-up, with email verification via sendEmailVerification().
- Social sign-in: use the platform SDKs (GoogleSignIn, sign_in_with_apple) and exchange provider tokens with Firebase Auth to link accounts.
- Phone auth: use verification codes with reCAPTCHA support on web; handle multi-factor edge cases.
- Custom tokens / enterprise: mint custom JWTs on trusted server and sign in with signInWithCustomToken().
Key Integration Pattern (Conceptual)
// sign in with email
final userCredential = await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
// token access
final idToken = await userCredential.user?.getIdToken();
- Token refresh: Firebase SDKs manage token refresh automatically; for server calls needing verification, send the ID token and verify it server-side (Firebase Admin SDK) to extract uid and claims.
- Secure storage: avoid duplicating tokens in plain storage; rely on Firebase SDK which stores tokens securely and refreshes them. If you need to persist custom tokens, use platform secure storage (flutter_secure_storage).
Best practice: Use Firebase custom claims for role-based authorization and assign them from a trusted Cloud Function (not from client).
Firestore — Modeling, Queries, Pagination, Transactions, and Offline
Modeling
Prefer collections of documents. Use subcollections for many-to-many or growing lists; avoid very large documents. Example structure:
- /courses/{courseId} (document)
- /courses/{courseId}/lessons/{lessonId} (subcollection)
- /users/{uid} (profile, favorites as list or small map)
Operations
- Reads/Writes: use batched writes for multi-document atomicity and transactions for read-modify-write semantics.
- Pagination: use cursors (startAfter / startAt) with orderBy on indexed fields; avoid offset-based pagination for large sets.
- Indexes: create composite indexes for compound queries; watch Firestore console for create-index errors and follow generated index definitions.
- Offline: Firestore SDK supports disk persistence; handle merge conflicts by making server-side reconciliation idempotent or use last-write-wins where appropriate.
- Security rules: validate structure, types, and ownership using rules (see section 6).
- Performance: limit document size, minimize read amplification (avoid reading parent documents for each child), and consider denormalization for read-heavy patterns.
Sample Firestore Operations (Conceptual)
// fetch page
final q = FirebaseFirestore.instance
.collection('courses')
.orderBy('publishedAt', descending: true)
.limit(pageSize);
final snapshot = await q.get();
final docs = snapshot.docs;
// transaction
await FirebaseFirestore.instance.runTransaction((tx) async {
final docRef = FirebaseFirestore.instance.doc('counters/visits');
final snap = await tx.get(docRef);
final newCount = (snap.data()?['count'] ?? 0) + 1;
tx.update(docRef, {'count': newCount});
});
Cloud Storage and File Workflows
- Uploads: use Firebase Storage SDK; upload files with metadata (contentType) and monitor progress.
- Security: use Storage Security Rules to restrict who can read/write which paths (e.g., /users/{uid}/avatar).
- Processing: offload heavy tasks to Cloud Functions triggered on finalize (e.g., image resizing, virus scanning).
- Serving: prefer download URLs for public assets or generate time-limited signed URLs for private access via callable function.
- Resumable uploads: use the native resumable upload support for large files and network resilience.
Conceptual Upload Example
final ref = FirebaseStorage.instance.ref('avatars/$uid.jpg');
final task = ref.putFile(file, SettableMetadata(contentType: 'image/jpeg'));
task.snapshotEvents.listen((event) {
final progress = event.bytesTransferred / event.totalBytes;
});
final url = await (await task).ref.getDownloadURL();
Cloud Function Resize Flow
On object finalize: download, resize, upload resized variant to /avatars/thumbs/{uid}.jpg, set metadata, and update Firestore profile doc with thumbnail URL.
Cloud Functions — Server-Side Rules, Triggers, and Callable Functions
- Triggers: Firestore document onCreate/onUpdate, Storage finalize, Auth user create, Pub/Sub schedules. Use them for derived data, notifications, or cleanup tasks.
- Callable functions: client calls functions via Firebase Functions SDK; they run with elevated privileges and can validate input and check auth context.
- Security: implement input validation, perform authorization checks using context.auth.uid or custom claims, and avoid returning sensitive data to unauthorized callers.
- Local testing: use the Emulator Suite to run functions locally and invoke them from your app in test mode.
Callable Function Sample (Conceptual Node.js)
exports.addFavorite = functions.https.onCall(async (data, context) => {
if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');
const uid = context.auth.uid;
const courseId = data.courseId;
// update Firestore securely
});
Firebase Cloud Messaging (FCM) — Notifications and Data Messages
Types
- Notification messages: displayed by OS when app is backgrounded; limited control on payload on Android/iOS.
- Data messages: delivered to app code (foreground/background via handlers) for full control; use for silent sync.
Token Lifecycle
Handle token refresh events and persist tokens on server for targeted sends (topic vs per-user).
Handling
- Foreground: implement onMessage handler to show in-app UI or local notification.
- Background/terminated: configure platform-specific handlers and notification channels (Android).
Server Sends
Use FCM HTTP v1 API or Admin SDK to send messages; use tokens or topics; set appropriate priority and TTL.
Best practice: Send data-only messages for critical sync with custom local notification display; use notification messages for marketing but combine with deep-link payloads.
Simplified Client Handler (Conceptual)
FirebaseMessaging.onMessage.listen((RemoteMessage msg) {
// show in-app banner or local notification
});
FirebaseMessaging.instance.getToken().then((token) {
// send token to server
});
Analytics and Crashlytics
- Analytics: instrument key events (screen_view, course_enrolled, search) and user properties (plan, role). Use suggested event names and limit custom events to meaningful actions.
- Crashlytics: integrate for crash reports; log non-fatal errors and attach custom keys for diagnostics (user id, course id).
- Linking: tie Firebase releases to Crashlytics and use breadcrumbs to replay user actions before a crash.
- Privacy: avoid logging PII; respect user opt-out consent and implement conditional initialization when required.
Example: Log Event
FirebaseAnalytics.instance.logEvent(
name: 'course_enrolled',
parameters: {'courseId': courseId}
);
Security Rules (Firestore & Storage) and Testing
Core Concepts
- Rules run as boolean functions to allow/deny reads/writes.
- Use
request.auth.uidto assert ownership. - Validate field existence and types to prevent malformed writes.
Firestore Rule Example
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow write: if request.auth != null && request.auth.uid == userId
&& request.resource.data.keys().hasAll(['displayName','avatarUrl'])
&& request.resource.data.displayName is string;
}
match /courses/{courseId} {
allow read: if true;
allow write: if request.auth != null && request.auth.token.admin == true;
}
}
}
Storage Rule Example
service firebase.storage {
match /b/{bucket}/o {
match /avatars/{userId}/{fileName} {
allow write: if request.auth != null && request.auth.uid == userId;
allow read: if resource.metadata.visibility == 'public' ||
(request.auth != null && request.auth.uid == userId);
}
}
}
Testing rules: Use the Firebase Emulator Suite and Firestore/Storage rule unit tests (firebase-tools / firebase emulators:start and the @firebase/rules-unit-testing npm package) to create deterministic rule tests in CI.
Emulator Suite, Local Testing, and CI Integration
Emulators Included
Auth, Firestore, Realtime DB, Storage, Functions, Pub/Sub, and Hosting. Use them to run integration tests without touching production.
Local Flow
- Start emulators:
firebase emulators:start --only auth,firestore,functions,storage - Point your Flutter app at emulators via SDK configuration (use emulator host and ports).
Flutter Example to Use Emulator Endpoints
// Firestore emulator
FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
// Auth emulator
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
CI Testing
Run the emulator in CI (docker image or hosted emulators), execute tests against it, and tear down. Use ephemeral project IDs and the Admin SDK with service account emulation where needed.
Benefits: Offline-safe, cost-free testing, deterministic integration tests for security rules and functions.
Deployment, Releases, and CI/CD for Firebase
- Functions: use
firebase deploy --only functionswith CI service account credentials; prefer staged deployments and rollback via versions if supported. - Rules and indexes: include rules and index definitions in repo and deploy with
firebase deploy. Test them in emulators first. - Hosting & Remote Config: deploy with CI and promote configs via controlled stages.
- Secrets and service accounts: store service account keys in CI secret stores; restrict scopes and rotate keys regularly. Use
gcloudIAM to grant minimal privileges.
Example GitHub Actions Steps (Conceptual)
Checkout, set up Node/Flutter, install Firebase CLI with auth via service account, run emulators for tests, then firebase deploy on main branch.
Mini-Project: Course Catalog with Firebase Backend (Step-by-Step)
Goal
Implement previously defined Course Catalog app using Firebase services.
Service Mapping
- Auth: Firebase Auth (email+Google) for sign-in.
- Courses: Firestore collection /courses with pagination via publishedAt and startAfter cursors.
- Favorites: per-user subcollection /users/{uid}/favorites or favorites list in user doc.
- Images: Storage path /courses/{courseId}/assets and /avatars/{uid}.
- Enrollment: Firestore writes with Cloud Function triggers to send welcome emails and update analytics.
- Offline: Firestore local persistence enabled, fallback to cached snapshot from local storage.
Implementation Highlights
- Set up Firebase project, enable Auth providers, create Firestore DB in production mode, and create Storage bucket.
- Use Firestore rules to enforce read access and only admin can create/edit courses.
- Use Cloud Function onWrite for course images to generate thumbnails and store urls in course doc.
- On client, wire Firebase SDKs, use Provider to expose services, and use Emulator configuration for local dev.
- Implement search using a combination of Firestore indexes and client-side filtering for rich text; consider Algolia if advanced search required.
Testing & Release
- Unit-test model parsing, rule tests via emulator, and functions via local functions emulator.
- Deploy functions and rules to staging project, smoke test, then promote to prod.
Exercises
1. Auth + Firestore integration
Implement sign-up and sign-in (email + Google). Save user profile to /users/{uid} and ensure security rules only allow owners to modify their profile. Test with emulator.
2. Firestore rules unit tests
Write rule tests that assert anonymous read-only, owner-only profile writes, and admin-only course writes. Run tests in CI using rules-unit-testing.
3. Cloud Function trigger
Implement a functions trigger that resizes uploaded course images and updates Firestore doc with thumbnail URL. Test locally with the emulator.
4. FCM flow
Implement server-side function to send FCM notification to users who favorited a course when instructor posts an update; test by sending messages from emulator and observing client handlers.
5. Emulator-driven integration test
Create an end-to-end test that uses the Emulator Suite: create user via Auth emulator, write a course, upload an image to Storage emulator, trigger function, and assert Firestore doc contains thumbnail URL.
Session Assignment
Complete Exercises 1–3. For each: Provide source code, emulator commands used, and unit tests for rules/functions. Include screenshots or logs showing emulator runs and successful function invocations. Write a short security rationale (150–300 words) explaining rule choices and how you prevent unauthorized data access.